//This code is released into the public domain by Jon Watte. No guarantee or 
//warranty is made as for the workings thereof, and/or the merchantability or 
//fitness for any particular purpose. All liability is disclaimed.
//This code file implements a simple profiler for XNA games that run on the Xbox. 
//It allows you to measure the amount of time spent in different parts of your code, 
//and bins the different durations into statistics bins (so you can see if it's 
//"spiky" or even).

//To use it, you simply include the class, and create static instances for the 
//parts you want to measure/report on.

//  static Profile ThisSection = Profile.Get("This.Section");

//Then, inside your code, you measure and "bill" time towards the instance:

//  public void MyFunction()
//  {
//    using (IDisposable d = ThisSection.Measure())
//    {
//      // code to measure goes here
//    }
//  }

//You can have as many such sections as you want. To print out the different 
//measurements, call Profile.Dump(), and it will be printed to the debug output. 
//You can also call DumpAndReset() to clear out the data after printing, and 
//start measurement from 0.

//  Profile.Dump();

//The overhead of this code is quite small. However, if you want to remove it, the 
//easiest way is to find the calls to Measure(), and comment out those lines using 
//an #if or single-line comment.


using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace Tomahawk.Runtime.Utils
{
    public class Profile
    {
        internal Profile(string n)
        {
            name_ = n;
            timestamp_ = Stopwatch.GetTimestamp();
            binLevels_[0] = Stopwatch.Frequency / 10000;
            binLevels_[1] = Stopwatch.Frequency / 1000;
            binLevels_[2] = Stopwatch.Frequency / 100;
            binLevels_[3] = Stopwatch.Frequency / 40;
            binLevels_[4] = Stopwatch.Frequency / 10;
        }

        internal void Reset()
        {
            time_ = 0;
            count_ = 0;
            maximum_ = 0;
            timestamp_ = Stopwatch.GetTimestamp();
            bins_ = new int[6];
        }

        internal string name_;
        internal long time_;
        internal long count_;
        internal long maximum_;
        internal int[] bins_ = new int[6];
        static internal long[] binLevels_ = new long[5];
        int nestLevel_;
        long startTime_;
        long timestamp_;

        public void Enter()
        {
            if (nestLevel_ == 0)
                startTime_ = Stopwatch.GetTimestamp();
            ++nestLevel_;
        }

        public void Exit()
        {
            --nestLevel_;
            if (nestLevel_ == 0)
            {
                ++count_;
                long l = Stopwatch.GetTimestamp() - startTime_;
                time_ += l;
                if (l > maximum_)
                    maximum_ = l;
                int i = 0;
                while (i < 4)
                {
                    if (binLevels_[i] > l)
                        break;
                    ++i;
                }
                bins_[i]++;
            }
            System.Diagnostics.Debug.Assert(nestLevel_ >= 0);
        }

        internal class Foo : IDisposable
        {
            static Foo foos_ = new Foo();
            Foo next_;
            Profile p_;

            Foo()
            {
            }

            Foo(Profile p)
            {
                p_ = p;
                p.Enter();
            }
            public void Dispose()
            {
                if (p_ != null)
                {
                    System.Diagnostics.Debug.Assert(next_ == null);
                    p_.Exit();
                    p_ = null;
                    next_ = foos_;
                    foos_ = this;
                }
            }

            ~Foo()
            {
                System.Diagnostics.Debug.Assert(p_ == null);
            }

            internal static Foo NewFoo(Profile p)
            {
                if (foos_ == null)
                    return new Foo(p);
                Foo ret = foos_;
                foos_ = ret.next_;
                ret.next_ = null;
                ret.p_ = p;
                p.Enter();
                return ret;
            }
        }

        //  you can use using() to measure time around a specific area
        //  and not have to worry about exceptions. This is fairly efficient, 
        //  and doesn't actually generate any garbage.
        public IDisposable Measure()
        {
            return Foo.NewFoo(this);
        }

        static Dictionary<string, Profile> profiles_ = new Dictionary<string, Profile>();

        public static Profile Get(string name)
        {
            Profile ret;
            if (profiles_.TryGetValue(name, out ret))
                return ret;
            ret = new Profile(name);
            profiles_.Add(name, ret);
            return ret;
        }

        public static string Dump(bool stats, bool histogram)
        {
            return Dump(stats, histogram, false);
        }

        public static void ResetAll()
        {
            foreach (Profile p in profiles_.Values)
            {
                p.Reset();
            }
        }

        public static string DumpAndReset(bool stats, bool histogram)
        {
            return Dump(stats, histogram, true);
        }

        internal static string Dump(bool stats, bool histogram, bool andReset)
        {
            long timestamp = Stopwatch.GetTimestamp();

            StringBuilder text = new StringBuilder();
            text.Append("--------------- Profile dump: ---------------\n");

            double freq = (double)Stopwatch.Frequency;

            if (stats)
            {
                text.Append("Profiler (time) # ms mean # count # ms max\n");
                foreach (Profile p in profiles_.Values)
                {
                    double elapsed = (timestamp - p.timestamp_) / freq;

                    text.AppendFormat("{0} ({1}%) # {2} # {3} # {4}\n",
                        p.name_,
                        (100.0 * p.time_ / (elapsed * freq)).ToString("N"),
                        (1000.0 * p.time_ / (p.count_ * freq)).ToString("F2"),
                        (p.count_ / elapsed).ToString("F2"),
                        (1000.0 * p.maximum_ / freq).ToString("F2"));
                }
            }

            if (histogram)
            {
                text.Append("\nHistogram (<0.1 # <1.0 # <10.0 # <25.0 <100.0 # ++ ms)\n");
                foreach (Profile p in profiles_.Values)
                {
                    text.AppendFormat("      {0} # {1} # {2} # {3} # {4} # {5}\n",
                        p.bins_[0],
                        p.bins_[1],
                        p.bins_[2],
                        p.bins_[3],
                        p.bins_[4],
                        p.bins_[5]);
                }
            }

            if (andReset)
            {
                foreach (Profile p in profiles_.Values)
                {
                    p.Reset();
                }
            }

            text.Append("--------------- End profile ---------------\n");

            return text.ToString();
        }
    }
}
